#include "ZComDef.h"
#include "OSAL.h"
#include "hal_uart.h"
#include "OSAL_Memory.h"
#include "OnBoard.h"
#include "ZGlobals.h"
#include "osal_Nv.h"


#include "mfg.h"
#include "cmd.h"
#include "cmd_uart.h"

/*
  Local Const
*/
#define CMD_PORT            ZAPP_PORT
#define CMD_MAX_RX_SIZE     HAL_UART_DMA_RX_MAX
#define CMD_MAX_TX_SIZE     HAL_UART_DMA_TX_MAX

/*
  Local Typedef
*/
typedef struct
{
  void* next;
  uint8 size;
  uint8 count;
  uint8 data[];
}cmd_uartTxList_t;

/*
  Local Function
*/
uint16 cmd_UartCheck(uint16 crc, uint8* buf, uint16 len);
void cmd_UartHandleCB( uint8 port, uint8 event );
void cmd_UartRx( uint8 port );
void cmd_UartTx( uint8 port );

/*
  Local Variable
*/
uint8 cmd_TaskId = 0xFF;
uint8 cmd_RxState = CMD_STATE_IDLE;
uint8 cmd_RxSize = 0;
uint8 cmd_LastChar = 0;
uint8 cmd_UartOutSeq = 0;

cmd_uartRxMsg_t* uartRxMsg = NULL;
cmd_uartTxList_t* uartTxList = NULL;
cmd_uartTxList_t* uartTxBuf = NULL;

CONST pCmdIndCB_t CmdIndCallbackTab[] = CMD_IND_ARRAY;

/*
  Function
*/

static uint16 calccrc(uint8 crcbuf,uint16 crc)
{
  uint8 i;
  uint8 chk;
  crc=crc ^ crcbuf;
  for(i=0;i<8;i++)
  {
    chk=crc&1; crc=crc>>1;
    crc=crc&0x7fff;
    if (chk==1)
      crc=crc^0xa001;
    crc=crc&0xffff;
  }
  return crc;
}

uint16 cmd_UartCheck(uint16 crc, uint8* buf, uint16 len)
{
  uint16 i;
  for(i=0;i<len;i++)
  {
    crc = calccrc(*buf,crc); buf++;
  }
  return crc;
}

void cmd_UartHandleCB ( uint8 port, uint8 event )
{
  if (event & HAL_UART_TX_EMPTY)
  {
    cmd_UartTx(port);
  }

  if (event & ( HAL_UART_RX_FULL | HAL_UART_RX_ABOUT_FULL | HAL_UART_RX_TIMEOUT))
  {
    cmd_UartRx(port);
  }
}

void cmd_UartRx(uint8 port)
{
  uint8 ch;
  osal_start_timerEx( cmd_TaskId, CMD_EVT_RX_TIMEOUT, 5 );
  while( HalUARTRead (port, &ch, 1) )
  {
    switch(cmd_RxState)
    {
    case CMD_STATE_IDLE:
      if( (cmd_LastChar == 0xA5) && (ch == 0x5A) )
      {
        cmd_RxState = CMD_STATE_START;
        cmd_LastChar = 0x00;
        break;
      }
      cmd_LastChar = ch;
      break;
      
    case CMD_STATE_START:
      {
        if( (uartRxMsg == NULL) && (ch >= 4 ) )
        {
          cmd_RxSize = ch;
          uartRxMsg = (cmd_uartRxMsg_t*)osal_msg_allocate(sizeof(cmd_uartRxMsg_t) + cmd_RxSize);
          if(uartRxMsg )
          {
            cmd_RxState = CMD_STATE_DATA;
            uartRxMsg->size = 0;
            break;
          }
        }
        cmd_RxState = CMD_STATE_ERRO;
      }
      break;
      
    case CMD_STATE_DATA:
      {
        uartRxMsg->data[uartRxMsg->size++] = ch;
        if(uartRxMsg->size >= cmd_RxSize)
        {
          uartRxMsg->hdr.event = SPI_INCOMING_ZAPP_DATA;
          uartRxMsg->hdr.status = ZSuccess;
          osal_msg_send( cmd_TaskId, (byte *)uartRxMsg );
          cmd_RxSize = 0;
          uartRxMsg = NULL;
          cmd_RxState = CMD_STATE_IDLE;
          osal_stop_timerEx(cmd_TaskId, CMD_EVT_RX_TIMEOUT);
          osal_clear_event(cmd_TaskId, CMD_EVT_RX_TIMEOUT);
        }
      }
      break;
    }
  }
}

void cmd_UartTx(uint8 port)
{
  if(uartTxBuf)
  {
    if(uartTxBuf->count < uartTxBuf->size) // tx is not finish
    {
      uint8 *txBuf = uartTxBuf->data + uartTxBuf->count;
      uint8 txLen = uartTxBuf->size - uartTxBuf->count;
      if(txLen > CMD_MAX_TX_SIZE) // continue tx next
      {
        uartTxBuf->count += CMD_MAX_TX_SIZE;
        txLen = CMD_MAX_TX_SIZE;
      }
      else
      {
        uartTxBuf->count = uartTxBuf->size;
      }
      HalUARTWrite(port, txBuf, txLen);
    }
    else // tx if finish
    {
      if( uartTxBuf )
      {
        osal_mem_free(uartTxBuf);
        uartTxBuf = NULL;
      }
      osal_start_timerEx( cmd_TaskId, CMD_EVT_TX_TRIGGER, 1 );
    }
  }
}

/*
  Global Function
*/

/*
  name: cmd_uartInit
*/
void cmd_uartInit(uint8 taskId)
{
  halUARTCfg_t uartConfig;
  /* Initialize APP ID */
  cmd_TaskId = taskId;
  /* UART Configuration */
  uartConfig.configured           = TRUE;
  uartConfig.baudRate             = HAL_UART_BR_115200;
  uartConfig.flowControl          = FALSE;
  uartConfig.flowControlThreshold = 32;
  uartConfig.rx.maxBufSize        = CMD_MAX_RX_SIZE;
  uartConfig.tx.maxBufSize        = CMD_MAX_TX_SIZE;
  uartConfig.idleTimeout          = 4;
  uartConfig.intEnable            = TRUE;
  uartConfig.callBackFunc         = cmd_UartHandleCB;
  HalUARTOpen (ZAPP_PORT, &uartConfig);
  cmd_UartOutSeq = osal_rand() & 0x7F;
}

void cmd_uartRxProcess(uint8 size, uint8* data)
{
  uint16 crc = BUILD_UINT16(data[size-2], data[size-1]);
  if (crc == cmd_UartCheck(CMD_CRC_INIT, data, size - 2))
  {
    bool ackCmd = (data[0] & 0x80)? true:false;
    uint8 cmdSeq = data[0] & 0x7F;
    uint8 cmdClass = data[1];
    uint8 cmdCount = sizeof(CmdIndCallbackTab)/sizeof(pCmdIndCB_t);
    if(ackCmd)
    {
      return;//CmdPkt_AckInd(cmdSeq, cmdClass);//process ack
    }
    else if(cmdClass < cmdCount)
    {
      if(CmdIndCallbackTab[cmdClass])
      {
        CmdIndCallbackTab[cmdClass](cmdSeq, cmdClass, size-4, data+2);
      }
    }
    else
    {
      cmd_UartAckSend(cmdSeq, cmdClass, 0, NULL);
    }
  }
  else
  {
    cmd_UartErroWarning( 0xFF );
  }
}

void cmd_uartRxTimeout(void)
{
  cmd_RxState = CMD_STATE_IDLE;
  cmd_LastChar = 0x00;
  if( uartRxMsg )
  {
    osal_msg_deallocate( (uint8*)uartRxMsg );
    uartRxMsg = NULL;
  }
  cmd_UartErroWarning( 0xFE );
}

void cmd_UartTxTrigger(void)
{
  if( uartTxList )
  {
    if( uartTxBuf == NULL)
    {
      uartTxBuf = uartTxList;
      uartTxList = uartTxList->next;
      uartTxBuf->next = NULL;
      if(uartTxBuf->size > CMD_MAX_TX_SIZE )
      {
        uartTxBuf->count = CMD_MAX_TX_SIZE;
      }
      else
      {
        uartTxBuf->count = uartTxBuf->size;
      }
      HalUARTWrite(ZAPP_PORT, uartTxBuf->data, uartTxBuf->count);
    }
  }
}

bool cmd_UartTxSend(uint8 len, uint8 *payload)
{
  cmd_uartTxList_t* item = osal_mem_alloc(sizeof(cmd_uartTxList_t) + 5 + len);
  if( item )
  {
    uint16 crc;
    item->next = NULL;
    item->size = 5 + len;
    item->count = 0;
    item->data[0] = 0xA5;
    item->data[1] = 0x5A;
    item->data[2] = len + 2;
    osal_memcpy( &item->data[3], payload, len );
    crc = cmd_UartCheck(CMD_CRC_INIT, &item->data[3], len);
    item->data[item->size-2] = LO_UINT16(crc);
    item->data[item->size-1] = HI_UINT16(crc);
    //push into tx list
    if( uartTxList == NULL )
    {
      uartTxList = item;
    }
    else
    {
      cmd_uartTxList_t* tail = uartTxList;
      while( tail->next )
      {
        tail = tail->next;
      }
      tail->next = item;
    }
    if ( uartTxList == item ) // 0 == osal_get_timeoutEx(cmd_TaskId, CMD_EVT_TX_TRIGGER)
    {
      osal_set_event( cmd_TaskId, CMD_EVT_TX_TRIGGER );
    }
    return TRUE;
  }
  return FALSE;
}

bool cmd_UartAckSend(uint8 cmdSeq, uint8 cmdClass, uint8 len, uint8* extData)
{
  uint8 ack_cmd[CMD_ACK_MAX_LEN];
  ack_cmd[0] = cmdSeq | 0x80;
  ack_cmd[1] = cmdClass;
  if(len > (CMD_ACK_MAX_LEN-2))
  {
    len = (CMD_ACK_MAX_LEN-2);
  }
  osal_memcpy(ack_cmd+2,extData,len);
  return cmd_UartTxSend(len+2, ack_cmd);
}

uint8 cmd_UartGetSeqNum(void)
{
  cmd_UartOutSeq ++;
  cmd_UartOutSeq &= 0x7F;
  return cmd_UartOutSeq;
}

void cmd_UartErroWarning(uint8 erro)
{
  uint8 erro_cmd[3];
  erro_cmd[0] = cmd_UartGetSeqNum();
  erro_cmd[1] = 0xFF;
  erro_cmd[2] = erro;
  cmd_UartTxSend(3, erro_cmd);
}
